package com.nervenets.web.controller.user;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import com.github.qcloudsms.SmsSingleSenderResult;
import com.nervenets.Global;
import com.nervenets.utils.*;
import com.nervenets.web.config.wx.WxMAConfiguration;
import com.nervenets.web.controller.BaseController;
import com.nervenets.web.controller.IdTokenParams;
import com.nervenets.web.controller.TokenParams;
import com.nervenets.web.controller.UserPagingParams;
import com.nervenets.web.entity.KeyValue;
import com.nervenets.web.entity.MessageCode;
import com.nervenets.web.entity.ResponseResult;
import com.nervenets.web.entity.UserToken;
import com.nervenets.web.enumeration.WealthType;
import com.nervenets.web.hibernate.dao.ManagerDao;
import com.nervenets.web.hibernate.dao.UserDao;
import com.nervenets.web.hibernate.dao.UserFavDao;
import com.nervenets.web.hibernate.dao.WealthFlowDao;
import com.nervenets.web.hibernate.domain.Manager;
import com.nervenets.web.hibernate.domain.WealthFlow;
import com.nervenets.web.hibernate.domain.user.User;
import com.nervenets.web.hibernate.domain.user.UserFav;
import com.nervenets.web.rabbitmq.FlowMessage;
import com.nervenets.web.rabbitmq.FlowType;
import com.nervenets.web.rabbitmq.MessageProducer;
import com.nervenets.web.redis.utils.RedisSyncLocker;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.persistence.criteria.Predicate;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Api(value = "用户接口v1", description = "用户相关接口")
@RestController
@RequestMapping("/api/v1/user")
public class UserController extends BaseController {
    @Resource
    private UserDao userDao;
    @Resource
    private UserFavDao userFavDao;
    @Resource
    private ManagerDao managerDao;
    @Resource
    private WealthFlowDao wealthFlowDao;
    @Resource
    private RedisSyncLocker redisSyncLocker;
    @Resource
    private MessageProducer messageProducer;

    @ApiOperation("获取用户资产余额")
    @PostMapping("/surplus")
    public ResponseResult surplus(@RequestBody TokenParams params) throws Exception {
        if (params.tokenIllegal()) return errorMessage(MessageCode.code_777);
        final User user = userDao.findById(params.resolveUserId()).orElse(null);
        if (null == user) return errorMessage(MessageCode.code_404, "用户不存在");
        final WealthFlow top = wealthFlowDao.findTopByUserIdAndWealthTypeOrderByIdDesc(user.getId(), WealthType.money);
        long surplus = null == top ? 0L : top.getSurplus();
        return successMessage(surplus);
    }

    @ApiOperation("小程序登录")
    @PostMapping("/waLogin")
    public ResponseResult waLogin(@RequestBody WaLoginParams params) throws Exception {
        final Set<ConstraintViolation<WaLoginParams>> errors = validator.validate(params);
        if (!errors.isEmpty()) {
            return errorMessage(errors);
        }

        final WxMaService maService = WxMAConfiguration.getMaService(params.getType()/*0*/);
        final WxMaJscode2SessionResult wxMaJscode2SessionResult = maService.jsCode2SessionInfo(params.getJsCode());
        final String openId = wxMaJscode2SessionResult.getOpenid();
        final String unionId = wxMaJscode2SessionResult.getUnionid();

        return (ResponseResult) redisSyncLocker.keyLock(openId, o -> {
            User user = userDao.findUserByOpenId(openId);
            if (null == user) {
                user = new User();
                user.setOpenId(openId);
                user.setUnionId(unionId);
                user.setInviteUserId(params.getInviteUserId());
            }
            if (!StringUtils.isEmpty(params.getAvatar())) user.getExtra().setWxAvatar(params.getAvatar());
            if (!StringUtils.isEmpty(params.getName())) user.getExtra().setWxName(params.getName());
            if (null != params.getGender()) user.setGender(params.getGender());
            user.setLastLoginTime(JodaUtils.getTimestamp());
            userDao.save(user);
            globalService.clearRedisUserStored(user.getId());

            return successMessage(user, new KeyValue("token", TokenParams.getTokenStr(new UserToken(user.getId()))));
        }, null);
    }

    @ApiOperation("手机号密码登录")
    @PostMapping("/mobileLogin")
    public ResponseResult mobileLogin(@RequestBody MobileLoginParams params) throws Exception {
        final Set<ConstraintViolation<MobileLoginParams>> errors = validator.validate(params);
        if (!errors.isEmpty()) {
            return errorMessage(errors);
        }

        final User user = userDao.findByMobile(params.getMobile());
        if (null == user) return errorMessage(MessageCode.code_404, "该手机号码未注册");
        if (!params.getPassword().equals(user.getPassword()))
            return errorMessage(MessageCode.code_444, "您输入的手机号码或密码不正确");

        return successMessage(user, new KeyValue("token", TokenParams.getTokenStr(new UserToken(user.getId()))));
    }

    @ApiOperation("手机号密码注册")
    @PostMapping("/mobileRegister")
    public ResponseResult mobileRegister(@RequestBody MobileRegisterParams params) throws Exception {
        final Set<ConstraintViolation<MobileRegisterParams>> errors = validator.validate(params);
        if (!errors.isEmpty()) {
            return errorMessage(errors);
        }

        if (!params.getPassword().equals(params.getPassword2())) {
            return errorMessage(MessageCode.code_444, "两次输入的密码不相同");
        }

        if (!String.valueOf(params.getValidationCode()).equals(globalService.getGlobalPair(String.format(Global.Constants.MOBILE_VALIDATION_CODE, params.getMobile())))) {
            return errorMessage(MessageCode.code_444, "您填写的验证码不正确或失效！");
        }

        User user = userDao.findByMobile(params.getMobile());
        if (null != user) return errorMessage(MessageCode.code_444, "该手机号码已注册");

        user = new User();
        user.setMobile(params.getMobile());
        user.setPassword(params.getPassword());
        userDao.save(user);

        return successMessage();
    }

    @ApiOperation("更新用户信息")
    @PostMapping("/updateUserInfo")
    public ResponseResult updateUserInfo(@RequestBody WaUpdateUserInfoParams params) {
        final Set<ConstraintViolation<WaUpdateUserInfoParams>> errors = validator.validate(params);
        if (!errors.isEmpty()) {
            return errorMessage(errors);
        }

        final User user = userDao.findById(params.resolveUserId()).orElse(null);
        if (null == user) return errorMessage(MessageCode.code_777);

        if (null != params.getGender()) {
            user.setGender(params.getGender());
        }
        if (!StringUtils.isEmpty(params.getWxName())) {
            user.getExtra().setWxName(params.getWxName());
        }
        if (!StringUtils.isEmpty(params.getWxAvatar())) {
            user.getExtra().setWxAvatar(params.getWxAvatar());
        }
        if (!StringUtils.isEmpty(params.getWxAccount())) {
            user.getExtra().setWxAccount(params.getWxAccount());
        }
        if (!StringUtils.isEmpty(params.getWxQrCodeUrl())) {
            user.getExtra().setWxQrCodeUrl(params.getWxQrCodeUrl());
        }
        if (!StringUtils.isEmpty(params.getIntroAudioUrl())) {
            user.getExtra().setIntroAudioUrl(params.getIntroAudioUrl());
        }
        if (!StringUtils.isEmpty(params.getDescription())) {
            user.getExtra().setDescription(params.getDescription());
        }
        if (!StringUtils.isEmpty(params.getTrueName())) {
            user.getExtra().setTrueName(params.getTrueName());
        }
        if (!StringUtils.isEmpty(params.getTrueAvatar())) {
            user.getExtra().setTrueAvatar(params.getTrueAvatar());
        }
        if (!StringUtils.isEmpty(params.getJobPosition())) {
            user.getExtra().setJobPosition(params.getJobPosition());
        }
        user.setUpdateTime(JodaUtils.getTimestamp());
        userDao.save(user);

        return successMessage();
    }

    @ApiOperation("绑定微信登录")
    @PostMapping("/wxLogin")
    public ResponseResult wxLogin(@RequestBody WeixinLoginParams params) {
        final Set<ConstraintViolation<WeixinLoginParams>> errors = validator.validate(params);
        if (!errors.isEmpty()) {
            return errorMessage(errors);
        }

        User user = userDao.findUserByOpenId(params.getOpenId());
        if (null == user) return errorMessage(MessageCode.code_404, "当前微信用户未注册或绑定");

        return successMessage(user, new KeyValue("token", TokenParams.getTokenStr(new UserToken(user.getId()))));
    }

    @ApiOperation("绑定微信")
    @PostMapping("/wxBind")
    public ResponseResult wxBind(@RequestBody WeixinBindParams params) {
        final Set<ConstraintViolation<WeixinBindParams>> errors = validator.validate(params);
        if (!errors.isEmpty()) {
            return errorMessage(errors);
        }

        final User user = userDao.findById(params.resolveUserId()).orElse(null);
        if (null == user) return errorMessage(MessageCode.code_777);

        User weixinUser = userDao.findUserByOpenId(params.getOpenId());
        if (null != weixinUser && weixinUser.getId() != params.resolveUserId()) {
            return errorMessage(MessageCode.code_444, "该微信已绑定到其他账号，不能再次绑定");
        }

        user.setOpenId(params.getOpenId());
        user.setUnionId(params.getUnionId());
        if (StringUtils.isEmpty(user.getExtra().getWxName())) user.getExtra().setWxName(params.getNickname());
        if (StringUtils.isEmpty(user.getExtra().getWxAvatar())) user.getExtra().setWxAvatar(params.getHeadImgUrl());
        user.setGender(params.getGender());
        userDao.save(user);
        globalService.clearRedisUserStored(user.getId());
        return successMessage(user, new KeyValue("token", TokenParams.getTokenStr(new UserToken(user.getId()))));
    }

    @ApiOperation("用户列表")
    @PostMapping("/list")
    public ResponseResult list(@RequestBody UserListPagingParams params) {
        final Set<ConstraintViolation<UserListPagingParams>> errors = validator.validate(params);
        if (!errors.isEmpty()) {
            return errorMessage(errors);
        }

        Specification<User> specification = (Specification<User>) (root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
            if (params.getTargetUserId() > 0) {
                predicates.add(criteriaBuilder.equal(root.get("id"), params.getTargetUserId()));
            }
            if (!StringUtils.isEmpty(params.getName())) {
                predicates.add(criteriaBuilder.like(root.get("name"), "%" + params.getName() + "%"));
            }
            if (!StringUtils.isEmpty(params.getMobile())) {
                predicates.add(criteriaBuilder.like(root.get("mobile"), "%" + params.getMobile() + "%"));
            }
            if (params.getStoreId() > 0) {
                predicates.add(criteriaBuilder.equal(root.get("storeId"), params.getStoreId()));
            }
            return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
        };
        Page<User> page = userDao.findAll(specification, PageRequest.of(params.getPage(), params.getLimit(), new Sort(Sort.Direction.DESC, "id")));
        List<UserModel> list = new ArrayList<>();
        page.map(user -> list.add(new UserModel(user, globalService)));
        return successMessage(list, new KeyValue("page", params.getPage()), new KeyValue("total", page.getTotalElements()));
    }

    @ApiOperation("用户详情")
    @PostMapping("/detail")
    public ResponseResult detail(@RequestBody IdTokenParams params) {
        final Set<ConstraintViolation<IdTokenParams>> errors = validator.validate(params);
        if (!errors.isEmpty()) {
            return errorMessage(errors);
        }

        final User user = userDao.findById(params.getTargetId()).orElse(null);
        if (null == user) return errorMessage(MessageCode.code_404, "目标用户不存在");

        final UserModel userModel = new UserModel(user, globalService);
        return successMessage(userModel);
    }

    @ApiOperation("用户详情")
    @PostMapping("/my")
    public ResponseResult my(@RequestBody TokenParams params, HttpServletRequest request) {
        final Set<ConstraintViolation<TokenParams>> errors = validator.validate(params);
        if (!errors.isEmpty()) {
            return errorMessage(errors);
        }

        final long userId = params.resolveUserId();
        if (0 == userId) return errorMessage(MessageCode.code_777);
        final User user = userDao.findById(userId).orElse(null);
        if (null == user) return errorMessage(MessageCode.code_777);

        if (!user.isEnable()) return errorMessage(MessageCode.code_444, "您的账号存在违规现象，已被管理员禁用。");

        final UserModel userModel = new UserModel(user, globalService);

        if (globalVariablesService.canProceedCustom(String.format("ACTIVE_USER_%s", userId), JodaUtils.getEndOfToday() - JodaUtils.getTimestamp())) {//一天只算一次活跃就可以了
            messageProducer.flow(new FlowMessage(userId, FlowType.userActive, HttpTools.getIp(request), 0));
        }

        return successMessage(userModel);
    }

    @ApiOperation("用户绑定手机号码")
    @PostMapping("/bindMobile")
    public ResponseResult bindMobile(@RequestBody BindMobileParams params) throws Exception {
        final Set<ConstraintViolation<BindMobileParams>> errors = validator.validate(params);
        if (!errors.isEmpty()) {
            return errorMessage(errors);
        }

        if (!String.valueOf(params.getValidationCode()).equals(globalService.getGlobalPair(String.format(Global.Constants.MOBILE_VALIDATION_CODE, params.getMobile())))) {
            return errorMessage(MessageCode.code_444, "您填写的验证码不正确或失效！");
        }
        return (ResponseResult) redisSyncLocker.idLock(params.resolveUserId(), o -> {
            final User user = userDao.findById(params.resolveUserId()).orElse(null);
            if (null == user) return errorMessage(MessageCode.code_404, "token无效，当前用户不存在");
            user.setMobile(params.getMobile());
            userDao.save(user);
            globalService.clearRedisUserStored(user.getId());
            return successMessage(user);
        });
    }

    @ApiOperation("验证码发送")
    @PostMapping("/validationCode")
    public ResponseResult validationCode(@RequestBody ValidationCodeMobileParams params) throws Exception {
        final Set<ConstraintViolation<ValidationCodeMobileParams>> errors = validator.validate(params);
        if (!errors.isEmpty()) {
            return errorMessage(errors);
        }

        if (params.isCheckRegister()) {
            final User user = userDao.findByMobile(params.getMobile());
            if (null == user) return errorMessage(MessageCode.code_404, "该手机号未注册");
        }

        final String validationCodeKey = String.format(Global.Constants.MOBILE_VALIDATION_CODE, params.getMobile());

        return (ResponseResult) redisSyncLocker.keyLock(validationCodeKey, o -> {
            String validationCode = globalService.getGlobalPair(validationCodeKey);
            if (!StringUtils.isEmpty(validationCode))
                return errorMessage(MessageCode.code_444, globalService.getGlobalPairExpired(validationCodeKey), "验证码已发送！");

            validationCode = String.valueOf(NumberUtil.randomNumber(100000, 999999));

            SmsSingleSenderResult result = QQUtils.sendMobileValidationCode(params.getMobile(), validationCode);
            if (0 == result.result) {
                globalService.setGlobalPair(validationCodeKey, validationCode, 5 * 60);
                return successMessage();
            }
            return errorMessage(MessageCode.code_444, result.errMsg);
        }, null);
    }

    @ApiOperation("用户启用或禁用")
    @PostMapping("/enable")
    public ResponseResult enable(@RequestBody UserEnableParams params) throws Exception {
        if (params.tokenIllegal()) return errorMessage(MessageCode.code_777);
        final Set<ConstraintViolation<UserEnableParams>> errors = validator.validate(params);
        if (!errors.isEmpty()) {
            return errorMessage(errors);
        }
        return (ResponseResult) redisSyncLocker.idLock(params.getTargetUserId(), o -> {
            User user = userDao.findById(params.getTargetUserId()).orElse(null);
            if (null == user) return errorMessage(MessageCode.code_404, "您操作的用户不存在");
            user.setEnable(params.isEnable());
            userDao.save(user);
            globalService.clearRedisUserStored(user.getId());
            return successMessage(user);
        });
    }

    @ApiOperation("加入团队")
    @PostMapping("/joinTeam")
    public ResponseResult joinTeam(@RequestBody JoinTeamParams params) throws Exception {
        if (params.tokenIllegal()) return errorMessage(MessageCode.code_777);
        final Set<ConstraintViolation<JoinTeamParams>> errors = validator.validate(params);
        if (!errors.isEmpty()) {
            return errorMessage(errors);
        }

        final Manager manager = managerDao.findById(params.getTeamId()).orElse(null);
        if (null == manager) return errorMessage(MessageCode.code_404, "团队不存在");

        return (ResponseResult) redisSyncLocker.idLock(params.resolveUserId(), o -> {
            final User user = userDao.findById(params.resolveUserId()).orElse(null);
            if (null == user) return errorMessage(MessageCode.code_777);

            if (null == user.getTeams()) user.setTeams(new HashSet<>());

            if (user.getTeams().contains(manager)) {
                return errorMessage(MessageCode.code_444, "已加入该团队，无需重复加入");
            }

            user.getTeams().add(manager);
            userDao.save(user);

            return successMessage();
        });
    }

    @ApiOperation("用户收藏")
    @PostMapping("/fav")
    public ResponseResult fav(@RequestBody IdTokenParams params) {
        if (params.tokenIllegal()) return errorMessage(MessageCode.code_777);
        final Set<ConstraintViolation<IdTokenParams>> errors = validator.validate(params);
        if (!errors.isEmpty()) {
            return errorMessage(errors);
        }

        User user = userDao.findById(params.getTargetId()).orElse(null);
        if (null == user) return errorMessage(MessageCode.code_404, "您要收藏的名片不存在");

        UserFav fav = userFavDao.findByUserIdAndUser(params.resolveUserId(), user);
        if (null == fav) fav = new UserFav(params.resolveUserId(), user);
        userFavDao.save(fav);

        return successMessage();
    }

    @ApiOperation("用户取消收藏")
    @PostMapping("/favCancel")
    public ResponseResult favCancel(@RequestBody IdTokenParams params) {
        if (params.tokenIllegal()) return errorMessage(MessageCode.code_777);
        final Set<ConstraintViolation<IdTokenParams>> errors = validator.validate(params);
        if (!errors.isEmpty()) {
            return errorMessage(errors);
        }
        User user = userDao.findById(params.getTargetId()).orElse(null);
        if (null == user) return errorMessage(MessageCode.code_404, "您要收藏的名片不存在");

        UserFav fav = userFavDao.findByUserIdAndUser(params.resolveUserId(), user);
        if (null == fav) userFavDao.delete(fav);

        return successMessage();
    }

    @ApiOperation("用户收藏列表")
    @PostMapping("/favs")
    public ResponseResult favs(@RequestBody UserPagingParams params) {
        if (params.tokenIllegal()) return errorMessage(MessageCode.code_777);
        final Set<ConstraintViolation<UserPagingParams>> errors = validator.validate(params);
        if (!errors.isEmpty()) {
            return errorMessage(errors);
        }
        Specification<UserFav> spec = (Specification<UserFav>) (root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
            predicates.add(criteriaBuilder.equal(root.get("removed"), 0));
            predicates.add(criteriaBuilder.equal(root.get("userId"), params.resolveUserId()));
            return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
        };
        final Page<UserFav> favs = userFavDao.findAll(spec, PageRequest.of(params.getPage(), params.getLimit(), new Sort(Sort.Direction.DESC, "id")));
        List<User> users = new ArrayList<>();
        favs.forEach(userFav -> {
            users.add(userFav.getUser());
        });
        return successMessage(users, new KeyValue("page", params.getPage()), new KeyValue("total", favs.getTotalElements()));
    }
}
